{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232",
      "metadata": {
        "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232"
      },
      "source": [
        "# State Reducers\n",
        "\n",
        "## Review\n",
        "\n",
        "We covered a few different ways to define LangGraph state schema, including `TypedDict`, `Pydantic`, or `Dataclasses`.\n",
        "\n",
        "## Goals\n",
        "\n",
        "Now, we're going to dive into reducers, which specify how state updates are performed on specific keys / channels in the state schema."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 60,
      "id": "02bae437",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "02bae437",
        "outputId": "e8d82193-af36-43c2-8b9d-8168b6b203bc"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "env: GOOGLE_API_KEY=AIzaSyCD_du_wlnoyfrBSnS9k-fyqcs-C2VKqI4\n"
          ]
        }
      ],
      "source": [
        "from google.colab import userdata\n",
        "\n",
        "%env GOOGLE_API_KEY = {userdata.get('GEMINI_API_KEY')}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 61,
      "id": "ac7a1af2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ac7a1af2",
        "outputId": "e4346d9d-197d-4fc1-e1cc-935bd0053a8d"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "AIzaSyCD_du_wlnoyfrBSnS9k-fyqcs-C2VKqI4\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "print(os.environ[\"GOOGLE_API_KEY\"])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 62,
      "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9",
      "metadata": {
        "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install --quiet -U langchain_core langgraph\n",
        "%pip install -q langchain-google-genai"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7",
      "metadata": {
        "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7"
      },
      "source": [
        "## Default overwriting state\n",
        "\n",
        "Let's use a `TypedDict` as our state schema."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 63,
      "id": "64e2438c-9353-4256-bc3c-1bb830374c0b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 251
        },
        "id": "64e2438c-9353-4256-bc3c-1bb830374c0b",
        "outputId": "4525808c-458e-4e95-e7a5-6ab590a46d18"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from typing_extensions import TypedDict\n",
        "from IPython.display import Image, display\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph.state import CompiledStateGraph\n",
        "\n",
        "class State(TypedDict):\n",
        "    foo: int\n",
        "\n",
        "def node_1(state: State) -> dict:\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": state['foo'] + 1}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(State)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "graph.invoke(State(foo= 1))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "U_8bel6AgBM6",
        "outputId": "5298bf18-c7dc-42d8-ba11-deae3026c931"
      },
      "id": "U_8bel6AgBM6",
      "execution_count": 64,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'foo': 2}"
            ]
          },
          "metadata": {},
          "execution_count": 64
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "775a099c-c41c-412f-8f05-e7436388ae79",
      "metadata": {
        "id": "775a099c-c41c-412f-8f05-e7436388ae79"
      },
      "source": [
        "Let's look at the state update, `return {\"foo\": state['foo'] + 1}`.\n",
        "\n",
        "As discussed before, by default LangGraph doesn't know the preferred way to update the state.\n",
        "\n",
        "So, it will just overwrite the value of `foo` in `node_1`:\n",
        "\n",
        "```\n",
        "return {\"foo\": state['foo'] + 1}\n",
        "```\n",
        "\n",
        "If we pass `{'foo': 1}` as input, the state returned from the graph is `{'foo': 2}`.\n",
        "\n",
        "## Branching\n",
        "\n",
        "Let's look at a case where our nodes branch."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 65,
      "id": "2b8d6ad4-2991-4325-933d-67057bc150f4",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "2b8d6ad4-2991-4325-933d-67057bc150f4",
        "outputId": "9d082144-ccc5-48aa-ddea-cec2ee2a640c"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "class State(TypedDict):\n",
        "    foo: int\n",
        "\n",
        "def node_1(state: State) -> dict:\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": state['foo'] + 1}\n",
        "\n",
        "def node_2(state: State) -> dict:\n",
        "    print(\"---Node 2---\")\n",
        "    return {\"foo\": state['foo'] + 1}\n",
        "\n",
        "def node_3(state: State) -> dict:\n",
        "    print(\"---Node 3---\")\n",
        "    return {\"foo\": state['foo'] + 1}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(State)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "builder.add_node(\"node_2\", node_2)\n",
        "builder.add_node(\"node_3\", node_3)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", \"node_2\")\n",
        "builder.add_edge(\"node_1\", \"node_3\")\n",
        "builder.add_edge(\"node_2\", END)\n",
        "builder.add_edge(\"node_3\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 66,
      "id": "106729b3",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "106729b3",
        "outputId": "a0e48502-c070-4360-954a-d8b4fa9cbf22"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 2---\n",
            "---Node 3---\n",
            "InvalidUpdateError occurred: At key 'foo': Can receive only one value per step. Use an Annotated key to handle multiple values.\n"
          ]
        }
      ],
      "source": [
        "from langgraph.errors import InvalidUpdateError\n",
        "try:\n",
        "    graph.invoke({\"foo\" : 1})\n",
        "except InvalidUpdateError as e:\n",
        "    print(f\"InvalidUpdateError occurred: {e}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe",
      "metadata": {
        "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe"
      },
      "source": [
        "We see a problem!\n",
        "\n",
        "Node 1 branches to nodes 2 and 3.\n",
        "\n",
        "Nodes 2 and 3 run in parallel, which means they run in the same step of the graph.\n",
        "\n",
        "They both attempt to overwrite the state *within the same step*.\n",
        "\n",
        "This is ambiguous for the graph! Which state should it keep?"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f1609cf7-dc47-4926-a154-77904b6cc550",
      "metadata": {
        "id": "f1609cf7-dc47-4926-a154-77904b6cc550"
      },
      "source": [
        "## Reducers\n",
        "\n",
        "[Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) give us a general way to address this problem.\n",
        "\n",
        "They specify how to perform updates.\n",
        "\n",
        "We can use the `Annotated` type to specify a reducer function.\n",
        "\n",
        "For example, in this case let's append the value returned from each node rather than overwriting them.\n",
        "\n",
        "We just need a reducer that can perform this: `operator.add` is a function from Python's built-in operator module.\n",
        "\n",
        "When `operator.add` is applied to lists, it performs list concatenation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 67,
      "id": "103d808c-55ec-44f2-a688-7b5e1572875a",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 251
        },
        "id": "103d808c-55ec-44f2-a688-7b5e1572875a",
        "outputId": "21fad045-a76a-4702-de15-0085ab2108d9"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from operator import add\n",
        "from typing import Annotated\n",
        "\n",
        "class State(TypedDict):\n",
        "    foo: Annotated[list[int], add]\n",
        "\n",
        "def node_1(state: State):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": [state['foo'][0] + 1]}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(State)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 68,
      "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf",
        "outputId": "c2642424-d1a6-4b7a-9860-423e852acd21"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'foo': [1, 2]}"
            ]
          },
          "metadata": {},
          "execution_count": 68
        }
      ],
      "source": [
        "graph.invoke({\"foo\" : [1]})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "63fbd6e0-0207-4049-b86d-c006cbba630b",
      "metadata": {
        "id": "63fbd6e0-0207-4049-b86d-c006cbba630b"
      },
      "source": [
        "Now, our state key `foo` is a list.\n",
        "\n",
        "This `operator.add` reducer function will append updates from each node to this list."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 69,
      "id": "768fd0ed-5e24-44a4-b14d-0e299310e105",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "768fd0ed-5e24-44a4-b14d-0e299310e105",
        "outputId": "294c2b87-38e0-4483-d0fa-298f2ac73d79"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "def node_1(state: State):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": [state['foo'][-1] + 1]}\n",
        "\n",
        "def node_2(state: State):\n",
        "    print(\"---Node 2---\")\n",
        "    return {\"foo\": [state['foo'][-1] + 1]}\n",
        "\n",
        "def node_3(state: State):\n",
        "    print(\"---Node 3---\")\n",
        "    return {\"foo\": [state['foo'][-1] + 1]}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(State)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "builder.add_node(\"node_2\", node_2)\n",
        "builder.add_node(\"node_3\", node_3)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", \"node_2\")\n",
        "builder.add_edge(\"node_1\", \"node_3\")\n",
        "builder.add_edge(\"node_2\", END)\n",
        "builder.add_edge(\"node_3\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5439baad-5a75-4188-b936-dbe74cdd9078",
      "metadata": {
        "id": "5439baad-5a75-4188-b936-dbe74cdd9078"
      },
      "source": [
        "We can see that updates in nodes 2 and 3 are performed concurrently because they are in the same step."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 70,
      "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb",
        "outputId": "ec6e98ef-1481-4ad3-a137-227e3f312020"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 2---\n",
            "---Node 3---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'foo': [1, 2, 3, 3]}"
            ]
          },
          "metadata": {},
          "execution_count": 70
        }
      ],
      "source": [
        "graph.invoke({\"foo\" : [1]})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "87faaa07-2955-4466-9bca-4b536e05f260",
      "metadata": {
        "id": "87faaa07-2955-4466-9bca-4b536e05f260"
      },
      "source": [
        "Now, let's see what happens if we pass `None` to `foo`.\n",
        "\n",
        "We see an error because our reducer, `operator.add`, attempts to concatenate `NoneType` pass as input to list in `node_1`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 71,
      "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a",
        "outputId": "ac91170f-81c8-42c5-99a1-d043dc5a2cff"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
          ]
        }
      ],
      "source": [
        "try:\n",
        "    graph.invoke({\"foo\" : None})\n",
        "except TypeError as e:\n",
        "    print(f\"TypeError occurred: {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6",
      "metadata": {
        "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6"
      },
      "source": [
        "## Custom Reducers\n",
        "\n",
        "To address cases like this, [we can also define custom reducers](https://langchain-ai.github.io/langgraph/how-tos/subgraph/#custom-reducer-functions-to-manage-state).\n",
        "\n",
        "For example, lets define custom reducer logic to combine lists and handle cases where either or both of the inputs might be `None`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 79,
      "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f",
      "metadata": {
        "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f"
      },
      "outputs": [],
      "source": [
        "def reduce_list(left: list | None, right: list | None) -> list:\n",
        "    \"\"\"Safely combine two lists, handling cases where either or both inputs might be None.\n",
        "\n",
        "    Args:\n",
        "        left (list | None): The first list to combine, or None.\n",
        "        right (list | None): The second list to combine, or None.\n",
        "\n",
        "    Returns:\n",
        "        list: A new list containing all elements from both input lists.\n",
        "               If an input is None, it's treated as an empty list.\n",
        "    \"\"\"\n",
        "    if not left:\n",
        "        left = []\n",
        "    if not right:\n",
        "        right = []\n",
        "    return left + right\n",
        "\n",
        "class DefaultState(TypedDict):\n",
        "    foo: Annotated[list[int], add]\n",
        "\n",
        "class CustomReducerState(TypedDict):\n",
        "    foo: Annotated[list[int], reduce_list]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635",
      "metadata": {
        "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635"
      },
      "source": [
        "In `node_1`, we append the value 2."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 88,
      "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 268
        },
        "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c",
        "outputId": "4a486df0-4910-4aca-94e1-c053aaae0696"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
          ]
        }
      ],
      "source": [
        "def node_1(state: DefaultState):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": [2]}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(DefaultState)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "\n",
        "try:\n",
        "    print(graph.invoke({\"foo\" : None}))\n",
        "except TypeError as e:\n",
        "    print(f\"TypeError occurred: {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf",
      "metadata": {
        "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf"
      },
      "source": [
        "Now, try with our custom reducer. We can see that no error is thrown."
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "def node_1(state: CustomReducerState):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"foo\": [2]}"
      ],
      "metadata": {
        "id": "sCakPhr-m1Co"
      },
      "id": "sCakPhr-m1Co",
      "execution_count": 89,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": 90,
      "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 286
        },
        "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2",
        "outputId": "aa33aef1-1fdd-45db-a2b7-40735c410152"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "{'foo': [2]}\n"
          ]
        }
      ],
      "source": [
        "# Build graph\n",
        "builder: StateGraph = StateGraph(CustomReducerState)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_edge(\"node_1\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))\n",
        "\n",
        "try:\n",
        "    print(graph.invoke({\"foo\" : None}))\n",
        "except TypeError as e:\n",
        "    print(f\"TypeError occurred: {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe",
      "metadata": {
        "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe"
      },
      "source": [
        "## Messages\n",
        "\n",
        "In module 1, we showed how to use a built-in reducer, `add_messages`, to handle messages in state.\n",
        "\n",
        "We also showed that [`MessagesState` is a useful shortcut if you want to work with messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#messagesstate).\n",
        "\n",
        "* `MessagesState` has a built-in `messages` key\n",
        "* It also has a built-in `add_messages` reducer for this key\n",
        "\n",
        "These two are equivalent.\n",
        "\n",
        "We'll use the `MessagesState` class via `from langgraph.graph import MessagesState` for brevity.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 91,
      "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3",
      "metadata": {
        "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3"
      },
      "outputs": [],
      "source": [
        "from typing import Annotated\n",
        "from langgraph.graph import MessagesState\n",
        "from langchain_core.messages import AnyMessage\n",
        "from langgraph.graph.message import add_messages\n",
        "\n",
        "# Define a custom TypedDict that includes a list of messages with add_messages reducer\n",
        "class CustomMessagesState(TypedDict):\n",
        "    messages: Annotated[list[AnyMessage], add_messages]\n",
        "    added_key_1: str\n",
        "    added_key_2: str\n",
        "    # etc\n",
        "\n",
        "# Use MessagesState, which includes the messages key with add_messages reducer\n",
        "class ExtendedMessagesState(MessagesState):\n",
        "    # Add any keys needed beyond messages, which is pre-built\n",
        "    added_key_1: str\n",
        "    added_key_2: str\n",
        "    # etc"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "287805e4-722a-4428-b040-2892b29de870",
      "metadata": {
        "id": "287805e4-722a-4428-b040-2892b29de870"
      },
      "source": [
        "Let's talk a bit more about usage of the `add_messages` reducer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 92,
      "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668",
        "outputId": "1e9e66b3-246f-4c4a-ae35-d9a1f3564582"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='37edc642-b86d-49a4-85f2-e7b84c114dd6'),\n",
              " HumanMessage(content=\"I'm looking for information on marine biology.\", additional_kwargs={}, response_metadata={}, name='Lance', id='21c0d6f7-183c-4507-90b9-51f996bef3d3'),\n",
              " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', additional_kwargs={}, response_metadata={}, name='Model', id='f524a5df-cdf3-4f32-b6ec-d0259797defd')]"
            ]
          },
          "metadata": {},
          "execution_count": 92
        }
      ],
      "source": [
        "from langgraph.graph.message import add_messages\n",
        "from langchain_core.messages import AIMessage, HumanMessage\n",
        "\n",
        "# Initial state\n",
        "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\"),\n",
        "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\")\n",
        "                   ]\n",
        "\n",
        "# New message to add\n",
        "new_message = AIMessage(content=\"Sure, I can help with that. What specifically are you interested in?\", name=\"Model\")\n",
        "\n",
        "# Test\n",
        "add_messages(initial_messages , new_message)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bc492370-0502-43e6-87cc-181c60b3dbdb",
      "metadata": {
        "id": "bc492370-0502-43e6-87cc-181c60b3dbdb"
      },
      "source": [
        "So we can see that `add_messages` allows us to append messages to the `messages` key in our state.\n",
        "\n",
        "### Re-writing\n",
        "\n",
        "Let's show some useful tricks when working with the `add_messages` reducer.\n",
        "\n",
        "If we pass a message with the same ID as an existing one in our `messages` list, it will get overwritten!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 93,
      "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47",
        "outputId": "5528a0ad-98e9-4523-a2f6-563ecc710685"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='1'),\n",
              " HumanMessage(content=\"I'm looking for information on whales, specifically\", additional_kwargs={}, response_metadata={}, name='Lance', id='2')]"
            ]
          },
          "metadata": {},
          "execution_count": 93
        }
      ],
      "source": [
        "# Initial state\n",
        "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\", id=\"1\"),\n",
        "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\", id=\"2\")\n",
        "                   ]\n",
        "\n",
        "# New message to add\n",
        "new_message = HumanMessage(content=\"I'm looking for information on whales, specifically\", name=\"Lance\", id=\"2\")\n",
        "\n",
        "# Test\n",
        "add_messages(initial_messages , new_message)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f06e7788-7054-4752-99fe-27ebb901f263",
      "metadata": {
        "id": "f06e7788-7054-4752-99fe-27ebb901f263"
      },
      "source": [
        "### Removal\n",
        "\n",
        "`add_messages` also [enables message removal](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/).\n",
        "\n",
        "For this, we simply use [RemoveMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.modifier.RemoveMessage.html) from `langchain_core`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 94,
      "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b",
        "outputId": "3918783f-7712-473c-88ff-90f76edcc208"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='1'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='2')]\n"
          ]
        }
      ],
      "source": [
        "from langchain_core.messages import RemoveMessage\n",
        "\n",
        "# Message list\n",
        "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n",
        "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n",
        "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n",
        "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n",
        "\n",
        "# Isolate messages to delete\n",
        "delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n",
        "print(delete_messages)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 95,
      "id": "2d250578-3ec0-452e-91c0-072d785d96db",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "2d250578-3ec0-452e-91c0-072d785d96db",
        "outputId": "254c4a7b-ac66-4ab4-d563-f6206098957c"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[AIMessage(content='So you said you were researching ocean mammals?', additional_kwargs={}, response_metadata={}, name='Bot', id='3'),\n",
              " HumanMessage(content='Yes, I know about whales. But what others should I learn about?', additional_kwargs={}, response_metadata={}, name='Lance', id='4')]"
            ]
          },
          "metadata": {},
          "execution_count": 95
        }
      ],
      "source": [
        "add_messages(messages , delete_messages)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5db095c5-6d9a-4e62-a097-0403797511f6",
      "metadata": {
        "id": "5db095c5-6d9a-4e62-a097-0403797511f6"
      },
      "source": [
        "We can see that mesage IDs 1 and 2, as noted in `delete_messages` are removed by the reducer.\n",
        "\n",
        "We'll see this put into practice a bit later."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.3"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}